package com.comut.crud.jms;

import com.comut.crud.util.Util;
import com.comut.lib.connection.MongoDBConnectionManager;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;

import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 *
 * Gets info for a certain driver in MongoDB Issue: IFL-1569
 *
 * @author Vitor Borrego
 */
@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destination",
            propertyValue = "queue/DriverInfoQueue"),
    @ActivationConfigProperty(propertyName = "acknowledgeMode",
            propertyValue = "Auto-acknowledge"),
    @ActivationConfigProperty(propertyName = "destinationType",
            propertyValue = "javax.jms.Queue")
})
public class DriverInfoMDB implements MessageListener {

    private MongoDBConnectionManager mongoDB;
    private Mongo mongoConnection;
    private DB mongoDatabase;
    private DBCollection hisDriverState;
    private Session sessionJMS;
    private MessageProducer producer;
    private Connection connectionJMS;
    private DBCollection driverLegalTime;

    /**
     * Issue: IFL-1569
     */
    @PostConstruct
    private void postConstruct() {
        try {
            this.checkMongoDBConnection();
            this.connectJMS();
        } catch (Exception ex) {
            Logger.getLogger(DriverInfoMDB.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Issue: IFL-1569
     */
    @PreDestroy
    private void preDestroy() {
        try {
            this.mongoDB.freeConnection(this.mongoConnection, this.mongoDatabase);
            this.sessionJMS.close();
            this.connectionJMS.close();
        } catch (Exception ex) {
            Logger.getLogger(DriverInfoMDB.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Issue: IFL-1569
     *
     * @param msg
     */
    public void onMessage(Message msg) {
        if (msg instanceof TextMessage) {
            this.checkMongoDBConnection();
            TextMessage textMessage = (TextMessage) msg;

            try {
                int driverId = textMessage.getIntProperty("driverId");
                String taskId = textMessage.getStringProperty("taskId");

                int mid = 0;
                int pos = 0;
                int state = 0;
                DriverRest lastDailyRest = null;
                DriverRest lastWeeklyRest = null;
                int reducedNumberOfRests = 0;
                int numberOfExtendedRestPeriods = 0;
                long drivingTimeCurrentJourney = 0;
                long drivingTimeCurrentWeek = 0;
                long drivingTimeCurrent14days = 0;

                Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
                int currYearWeek = Integer.parseInt(String.format("%d%d", cal.get(Calendar.YEAR), cal.get(Calendar.WEEK_OF_YEAR)));
                cal.add(Calendar.WEEK_OF_YEAR, -1);
                int prevYearWeek = Integer.parseInt(String.format("%d%d", cal.get(Calendar.YEAR), cal.get(Calendar.WEEK_OF_YEAR)));

                BasicDBObject fieldsHisDriverState = new BasicDBObject();
                fieldsHisDriverState.put("_id", 0);
                fieldsHisDriverState.put("state", 1);
                fieldsHisDriverState.put("pos", 1);
                fieldsHisDriverState.put("mid", 1);

                BasicDBObject queryMongo = new BasicDBObject();
                queryMongo.put("did", driverId);
                DBObject result = this.hisDriverState.findOne(queryMongo, fieldsHisDriverState);


                if (result != null) {
                    mid = Integer.parseInt(result.get("mid").toString());
                    pos = Integer.parseInt(result.get("pos").toString());
                    state = Integer.parseInt(result.get("state").toString());
                }

                List<DBObject> resultsYearWeek = getDriverLegalTimeYearWeek(driverId);
                lastDailyRest = getLastDailyRest(driverId, resultsYearWeek);
                lastWeeklyRest = getLastWeeklyRest(driverId, resultsYearWeek);
                reducedNumberOfRests = getNumberReducedRestsCurrentWeek(resultsYearWeek, currYearWeek);
                numberOfExtendedRestPeriods = getNumberOfExtendedRestPeriodsCurrentWeek(resultsYearWeek, currYearWeek);
                drivingTimeCurrentJourney = getDrivingTimeCurrentJourney(resultsYearWeek, currYearWeek);
                drivingTimeCurrentWeek = getDrivingTimeCurrentWeek(resultsYearWeek, currYearWeek);
                drivingTimeCurrent14days = getDrivingTimeCurrent14days(resultsYearWeek, currYearWeek, prevYearWeek);

                TextMessage replyMessage = sessionJMS.createTextMessage("Reply");
                replyMessage.setStringProperty("taskId", taskId);
                replyMessage.setIntProperty("mid", mid);
                replyMessage.setIntProperty("pos", pos);
                replyMessage.setIntProperty("state", state);
                replyMessage.setIntProperty("driverId", driverId);
                replyMessage.setLongProperty("lastDailyRest", -1);
                replyMessage.setLongProperty("lastWeeklyRest", -1);

                if (lastDailyRest != null) {
                    replyMessage.setLongProperty("lastDailyRest", lastDailyRest.getBreakStart().getTime());
                }

                if (lastWeeklyRest != null) {
                    replyMessage.setLongProperty("lastWeeklyRest", lastWeeklyRest.getBreakStart().getTime());
                }

                replyMessage.setIntProperty("reducedNumberOfRests", reducedNumberOfRests);
                replyMessage.setIntProperty("numberOfExtendedRestPeriods", numberOfExtendedRestPeriods);
                replyMessage.setLongProperty("drivingTimeCurrentJourney", drivingTimeCurrentJourney);
                replyMessage.setLongProperty("drivingTimeCurrentWeek", drivingTimeCurrentWeek);
                replyMessage.setLongProperty("drivingTimeCurrentForthnight", drivingTimeCurrent14days); // current last 14 days

                this.producer.send(replyMessage, DeliveryMode.NON_PERSISTENT, 9, 10000); //TTL 2000[ms]

            } catch (Exception ex) {
                Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    /**
     * Issue: IFL-1569
     *
     * @param driverId
     * @return
     */
    private List<DBObject> getDriverLegalTimeYearWeek(int driverId) {
        BasicDBObject query = new BasicDBObject();
        query.put("did", driverId);
        DBCursor cursor = this.driverLegalTime.find(query);
        BasicDBObject sort = new BasicDBObject();
        sort.put("week_of_year", -1); // sort week_of_year descendng   , most recent at top    
        cursor.sort(sort);
        cursor.limit(2); // first two found weeks
        List<DBObject> results = cursor.toArray();
        return results;
    }

    /**
     * Issue: IFL-1569
     *
     * @param driverId
     * @param resultsYearWeek
     * @return
     */
    private DriverRest getLastWeeklyRest(int driverId, List<DBObject> resultsYearWeek) {
        ArrayList<DriverRest> listDriverRest = new ArrayList<DriverRest>();
        DriverRest ret = null;

        for (DBObject obj : resultsYearWeek) {
            BasicDBObject weeklyRest = (BasicDBObject) obj.get("rest");

            if (weeklyRest.get("type").toString().compareTo("none") != 0) {
                Date lastWeeklyRest = (Date) weeklyRest.get("date");
                DriverRest dr = new DriverRest();
                dr.setBreakStart(lastWeeklyRest);
                dr.setDriverId(driverId);
                listDriverRest.add(dr);
            }
        }

        Collections.sort(listDriverRest);

        if (listDriverRest.size() > 0) {
            ret = listDriverRest.get(listDriverRest.size() - 1);
        }

        return ret;
    }

    /**
     * Issue: IFL-1569
     *
     * @param resultsYearWeek
     * @param currYearWeek
     * @return
     */
    private int getNumberReducedRestsCurrentWeek(List<DBObject> resultsYearWeek, int currYearWeek) {
        /* get number of reduced rests for the current week */
        int reducedNumberRests = 0;

        if (resultsYearWeek.size() > 0) {
            HashMap hm = new HashMap();
            DBObject yearWeek = resultsYearWeek.get(0);

            if (((Integer) yearWeek.get("week_of_year")) == currYearWeek) {
                BasicDBList reducedJourneyRest = (BasicDBList) yearWeek.get("reduced_journey_rest");

                for (Object item : reducedJourneyRest) {
                    Date datex = (Date) item;

                    if (hm.containsKey(datex.getTime()) == false) {
                        hm.put(datex.getTime(), datex);
                    }
                }

                reducedNumberRests = hm.size();
            }
        }

        return reducedNumberRests;
    }

    /**
     * Issue: IFL-1569
     *
     * @param resultsYearWeek
     * @param currYearWeek
     * @return
     */
    private int getNumberOfExtendedRestPeriodsCurrentWeek(List<DBObject> resultsYearWeek, int currYearWeek) {
        /* get number of extended rest periods for the current week */
        int numberOfExtendedRestPeriods = 0;

        if (resultsYearWeek.size() > 0) {
            HashMap hm = new HashMap();
            DBObject yearWeek = resultsYearWeek.get(0);

            if (((Integer) yearWeek.get("week_of_year")) == currYearWeek) {
                BasicDBList extendedDrive = (BasicDBList) yearWeek.get("extended_drive");

                for (Object item : extendedDrive) {
                    Date datex = (Date) item;

                    if (hm.containsKey(datex.getTime()) == false) {
                        hm.put(datex.getTime(), datex);
                    }
                }

                numberOfExtendedRestPeriods = hm.size();
            }
        }

        return numberOfExtendedRestPeriods;
    }

    /**
     * Issue: IFL-1569
     *
     * @param resultsYearWeek
     * @param currYearWeek
     * @return
     */
    private long getDrivingTimeCurrentJourney(List<DBObject> resultsYearWeek, int currYearWeek) {
        long drivingTimeCurrentJourney = 0;

        if (resultsYearWeek.size() > 0) {
            DBObject yearWeek = resultsYearWeek.get(0);

            if (((Integer) yearWeek.get("week_of_year")) == currYearWeek) {
                BasicDBList journeys = (BasicDBList) yearWeek.get("journeys");
                DBObject journey = (DBObject) journeys.get(journeys.size() - 1);
                drivingTimeCurrentJourney = (Long) journey.get("drive");
            }
        }

        return drivingTimeCurrentJourney;
    }

    /**
     * Issue: IFL-1569
     *
     * @param resultsYearWeek
     * @param currYearWeek
     * @return
     */
    private long getDrivingTimeCurrentWeek(List<DBObject> resultsYearWeek, int currYearWeek) {
        long drivingTimeCurrentWeek = 0;

        if (resultsYearWeek.size() > 0) {
            DBObject yearWeek = resultsYearWeek.get(0);

            if (((Integer) yearWeek.get("week_of_year")) == currYearWeek) {
                drivingTimeCurrentWeek = (Long) yearWeek.get("drive_time");
            }
        }

        return drivingTimeCurrentWeek;
    }

    /**
     * Issue: IFL-1569
     *
     * @param resultsYearWeek
     * @param currYearWeek
     * @param prevYearWeek
     * @return
     */
    private long getDrivingTimeCurrent14days(List<DBObject> resultsYearWeek, int currYearWeek, int prevYearWeek) {
        long drivingTimeCurrent14days = 0;

        if (resultsYearWeek.size() >= 2) {
            DBObject yearWeek = resultsYearWeek.get(0);
            DBObject yearWeekPrev = resultsYearWeek.get(1);

            if (((Integer) yearWeek.get("week_of_year")) == currYearWeek && ((Integer) yearWeekPrev.get("week_of_year")) == prevYearWeek) {
                long curr = (Long) yearWeek.get("drive_time");
                long prev = (Long) yearWeekPrev.get("drive_time");
                drivingTimeCurrent14days = curr + prev;
            }
        }

        return drivingTimeCurrent14days;
    }

    /**
     * Issue: IFL-1569
     *
     * @throws UnknownHostException
     */
    private void connectMongoDB() throws UnknownHostException {
        this.mongoDB = MongoDBConnectionManager.instanceFromSystemProperties();
        this.mongoConnection = mongoDB.getConnection();
        this.mongoDatabase = mongoDB.getDB(this.mongoConnection);
        this.hisDriverState = this.mongoDatabase.getCollection("his_driver_state");
        this.driverLegalTime = this.mongoDatabase.getCollection("driver_legal_time");
    }

    /**
     * Issue: IFL-1569
     *
     * @throws NamingException
     * @throws JMSException
     */
    private void connectJMS() throws NamingException, JMSException {
        InitialContext ic = new InitialContext();
        ConnectionFactory cf = (ConnectionFactory) ic.lookup("/ConnectionFactory");
        Queue driverInfoReplyQueue = (Queue) ic.lookup("/queue/DriverInfoReplyQueue");
        this.connectionJMS = (javax.jms.Connection) cf.createConnection();
        this.sessionJMS = (Session) connectionJMS.createSession(false, Session.AUTO_ACKNOWLEDGE);
        this.producer = sessionJMS.createProducer(driverInfoReplyQueue);
        connectionJMS.start();
    }

    /**
     * Issue: IFL-1569
     */
    private class DriverRest implements Comparable<DriverRest> {

        private Integer driverId;
        private Integer journeyId;
        private Date start;
        private Date end;
        private String breakType;
        private Integer breakDuration;
        private Date breakStart;

        /**
         * @return the driverId
         */
        public Integer getDriverId() {
            return driverId;
        }

        /**
         * @param driverId the driverId to set
         */
        public void setDriverId(Integer driverId) {
            this.driverId = driverId;
        }

        /**
         * @return the journeyId
         */
        public Integer getJourneyId() {
            return journeyId;
        }

        /**
         * @param journeyId the journeyId to set
         */
        public void setJourneyId(Integer journeyId) {
            this.journeyId = journeyId;
        }

        /**
         * @return the start
         */
        public Date getStart() {
            return start;
        }

        /**
         * @param start the start to set
         */
        public void setStart(Date start) {
            this.start = start;
        }

        /**
         * @return the end
         */
        public Date getEnd() {
            return end;
        }

        /**
         * @param end the end to set
         */
        public void setEnd(Date end) {
            this.end = end;
        }

        /**
         * @return the breakType
         */
        public String getBreakType() {
            return breakType;
        }

        /**
         * @param breakType the breakType to set
         */
        public void setBreakType(String breakType) {
            this.breakType = breakType;
        }

        /**
         * @return the breakDuration
         */
        public Integer getBreakDuration() {
            return breakDuration;
        }

        /**
         * @param breakDuration the breakDuration to set
         */
        public void setBreakDuration(Integer breakDuration) {
            this.breakDuration = breakDuration;
        }

        /**
         *
         * @param breakStart
         */
        public void setBreakStart(Date breakStart) {
            this.breakStart = breakStart;
        }

        /**
         *
         * @return
         */
        public Date getBreakStart() {
            return this.breakStart;
        }

        /**
         *
         * @return
         */
        @Override
        public String toString() {
            return String.format("DriverId:%d JourneyId:%s Start:%s End:%s Type:%s Duration:%d BreakStart:%s", driverId, journeyId.toString(), Util.getDate(start), Util.getDate(end), breakType, breakDuration, Util.getDate(breakStart));
        }

        public int compareTo(DriverRest other) {
            return this.breakStart.compareTo(other.getBreakStart());
        }
    }

    /**
     * Issue: IFL-1569
     *
     * @param driverId
     * @param resultsYearWeek
     * @return
     * @throws NumberFormatException
     */
    private DriverRest getLastDailyRest(int driverId, List<DBObject> resultsYearWeek) throws NumberFormatException {
        ArrayList<DriverRest> listDriverRest = new ArrayList<DriverRest>();
        this.addDriverRest(resultsYearWeek, driverId, listDriverRest);
        // extract from listDriverRest the most recent breakStart

        Collections.sort(listDriverRest);
        DriverRest lastDailyRest = null;

        if (listDriverRest.size() > 0) {
            lastDailyRest = listDriverRest.get(listDriverRest.size() - 1);
        }

        return lastDailyRest;
    }

    /**
     * Issue: IFL-1569
     *
     * @param results
     * @param driverId
     * @param list
     */
    private void addDriverRest(List<DBObject> results, int driverId, ArrayList<DriverRest> list) {
        for (DBObject obj : results) {
            //Logger.getLogger(this.getClass().getName()).log(Level.INFO, String.format("%s", obj.toString() ));
            BasicDBList journeys = (BasicDBList) obj.get("journeys");

            for (Object journey : journeys) {
                DBObject journeyDoc = ((DBObject) journey);
                DriverRest dr = new DriverRest();

                dr.setDriverId(driverId);

                if (journeyDoc.containsField("id")) {
                    if (journeyDoc.get("id") != null) {
                        dr.setJourneyId((Integer) journeyDoc.get("id"));
                    }
                }

                if (journeyDoc.containsField("start")) {
                    if (journeyDoc.get("start") != null) {
                        dr.setStart((Date) journeyDoc.get("start"));
                    }
                }

                if (journeyDoc.containsField("end")) {
                    if (journeyDoc.get("end") != null) {
                        dr.setEnd((Date) journeyDoc.get("end"));
                    }
                }

                if (journeyDoc.containsField("break")) {
                    if (journeyDoc.get("break") != null) {
                        DBObject breakx = (DBObject) journeyDoc.get("break");
                        dr.setBreakType(breakx.get("type").toString());
                        if (breakx.get("duration") != null) {
                            dr.setBreakDuration((Integer) breakx.get("duration"));
                        }
                    }
                }

                // has a break/rest 
                if (dr.getBreakType().compareTo("none") != 0) {
                    Calendar endCal = Calendar.getInstance();
                    endCal.setTime(dr.getEnd());
                    endCal.add(Calendar.SECOND, dr.getBreakDuration() * -1);
                    Date breakStart = endCal.getTime();
                    dr.setBreakStart(breakStart);
                    list.add(dr);
                    //Logger.getLogger(this.getClass().getName()).log(Level.INFO, String.format("%s", dr.toString()));
                }
            }

        }
    }

    /**
     * Issue: IFL-1569 Checks the mongoDB connection
     */
    private void checkMongoDBConnection() {
        if (this.mongoDB == null || this.mongoDB.isAlive(this.mongoConnection) == false) {
            try {
                this.connectMongoDB();
            } catch (UnknownHostException ex) {
                Logger.getLogger(VehicleInfoMDB.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
